package com.sky.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.RedisConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.CategoryDTO;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import com.sky.entity.Dish;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.CategoryMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.CategoryService;
import com.sky.service.IDishService;
import com.sky.utils.RedisCacheUtil;
import com.sky.vo.LogicalField;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 分类业务层
 */
@Service
@Slf4j
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Resource
    private CategoryMapper categoryMapper;
    @Resource
    private IDishService iDishService;
    @Resource
    private SetmealMapper setmealMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisCacheUtil redisCacheUtil;

    // 注入已创建的线程池
    @Resource
    private ExecutorService FIXED_THREAD_POOL;
    /**
     * 新增分类
     *
     * @param categoryDTO
     */
    public void save(CategoryDTO categoryDTO) {
        Category category = new Category();
        //属性拷贝
        BeanUtils.copyProperties(categoryDTO, category);

        //分类状态默认为禁用状态0
        category.setStatus(StatusConstant.DISABLE);

        //设置创建时间、修改时间、创建人、修改人
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());
        Long id = BaseContext.getCurrentId().getId();
        category.setCreateUser(id);
        category.setUpdateUser(id);

        categoryMapper.insert(category);
    }

    /**
     * 分页查询
     *
     * @param categoryPageQueryDTO
     * @return
     */
    public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
        PageHelper.startPage(categoryPageQueryDTO.getPage(), categoryPageQueryDTO.getPageSize());
        //下一条sql进行分页，自动加入limit关键字分页
        Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO);
        return new PageResult(page.getTotal(), page.getResult());
    }

    /**
     * 根据id删除分类
     *
     * @param id
     */
    public void deleteById(Long id) {
        //查询当前分类是否关联了菜品，如果关联了就抛出业务异常
        Integer count = iDishService.lambdaQuery().eq(id != null, Dish::getCategoryId, id).count();
        if (count > 0) {
            //当前分类下有菜品，不能删除
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
        }

        //查询当前分类是否关联了套餐，如果关联了就抛出业务异常
        count = setmealMapper.countByCategoryId(id);
        if (count > 0) {
            //当前分类下有菜品，不能删除
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
        }

        //删除分类数据
        categoryMapper.deleteById(id);
    }

    /**
     * 修改分类
     *
     * @param categoryDTO
     */
    public void update(CategoryDTO categoryDTO) {
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO, category);

        //设置修改时间、修改人
        category.setUpdateTime(LocalDateTime.now());
        category.setUpdateUser(BaseContext.getCurrentId().getId());

        categoryMapper.update(category);
    }

    /**
     * 启用、禁用分类
     *
     * @param status
     * @param id
     */
    public void startOrStop(Integer status, Long id) {
        Category category = Category.builder().id(id).status(status).updateTime(LocalDateTime.now()).updateUser(BaseContext.getCurrentId().getId()).build();
        categoryMapper.update(category);
    }

    /**
     * 根据类型查询分类
     *
     * @param type
     * @return
     */
    public Result<List<Category>> list(Integer type) {
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        //redis中数据的key
        String key = RedisConstant.CATEGORY_TYPE_KEY + type;
        //查询redis
        String json = opsForValue.get(key);
        //穿透防护,如果查到的是""值
        if ("".equals(json)) {
            System.out.println("查到了长度为0的字符串的数据,就是缓冲穿透数据直接返回");
            return Result.success(Collections.emptyList());
        }
        if (StrUtil.isBlank(json)) {
            //如果数据不存在就尝试获取锁更新Redis
            //定义锁的id
            String lockKey = "lock:key" + key;
            boolean tryLock = redisCacheUtil.tryLock(lockKey);
            if (!tryLock) {
                //如果没有获取到锁,表示有人正在更新Redis了
                System.out.println("时间过期了,没有获取到锁,表示有人正在更新Redis了,返回过期的信息");
                return Result.success(Collections.emptyList());
            }
            // 定义方法开启新线程执行查询数据库,更新redis的任务
            getDatabase(type, key, lockKey);
            //如果没有数据直接返回
            System.out.println("没有数据开启线程执行更新,本次直接返回");
            return Result.success(Collections.emptyList());
        }
        //如果查到了数据,转换成对象
        LogicalField logicalField = JSONUtil.toBean(json, LogicalField.class);
        String data = JSONUtil.toJsonStr(logicalField.getData());
        System.out.println("查到了数据:" + data);
        List<Category> categoryList = JSONUtil.toList(data, Category.class);
        log.info("查询到的数据:{}", categoryList);
        //获取逻辑过期时间字段
        LocalDateTime LogicTime = logicalField.getLogicTime();
        //比较现在的时间判断是否过期
        if (LogicTime.isAfter(LocalDateTime.now())) {
            //如果逻辑过期时间在现在时间之后,则没有过期,直接返回信息
            System.out.println("逻辑过期时间在现在时间之后,没有过期,返回商铺信息");
            return Result.success(categoryList);
        }
        //如果过期了就尝试获取锁更新Redis
        //定义锁的id
        String lockKey = "lock:key" + key;
        boolean tryLock = redisCacheUtil.tryLock(lockKey);
        if (!tryLock) {
            //如果没有获取到锁,表示有人正在更新Redis了
            System.out.println("时间过期了,没有获取到锁,表示有人正在更新Redis了,返回过期的信息");
            return Result.success(categoryList);
        }
        // 定义方法开启新线程执行查询数据库,更新redis的任务
        getDatabase(type, key, lockKey);
        //本身直接返回过期的信息;
        System.out.println("获取到锁了,开启新线程去执行更新redis数据的逻辑,本线程直接返回过期的信息");
        return Result.success(categoryList);
    }

    /**
     * 定义方法开启新线程执行查询数据库,更新redis的任务
     *
     * @author: XieMingHui
     * @param: [type, key, lockKey]
     * @return: void
     **/
    private void getDatabase(Integer type, String key, String lockKey) {
       // ExecutorService FIXED_THREAD_POOL = Executors.newFixedThreadPool(1);
        //如果获取到锁了,开启新的线程去查询数据库完成更新redis操作
        FIXED_THREAD_POOL.submit(() -> {
            try {
                //查询数据库
                List<Category> list = categoryMapper.list(type);
                if (list.size() == 0) {
                    //数据库没有该数据,防止缓存穿透,存入空串
                    stringRedisTemplate.opsForValue().set(key, "", 2, TimeUnit.MINUTES);
                }
                //更新redis中逻辑过期字段
                redisCacheUtil.setRedis(key, list, RedisConstant.LOGIC_TIME, TimeUnit.DAYS);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                System.out.println("查询数据库,成功更新redis,删除存入的锁,释放锁=====================================");
                //删除存入的数据
                stringRedisTemplate.delete(lockKey);
            }
        });
    }
}
